package co.baiku.boot.weixin;

import co.baiku.boot.common.message.Message;
import co.baiku.boot.common.tools.JsonTools;
import co.baiku.boot.common.tools.ServletTools;
import co.baiku.boot.common.tools.StringTools;
import co.baiku.boot.config.WeixinPayConfig;
import co.baiku.boot.handle.WeixinRefundHandle;
import co.baiku.boot.request.*;
import co.baiku.boot.response.*;
import co.baiku.boot.response.item.WeixinRefundNoticeRecord;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;

/**
 * microPay	刷卡支付
 * unifiedOrder	统一下单
 * orderQuery	查询订单
 * reverse	撤销订单
 * closeOrder	关闭订单
 * refund	申请退款
 * refundQuery	查询退款
 * downloadBill	下载对账单
 * report	交易保障
 * shortUrl	转换短链接
 * authCodeToOpenid	授权码查询openid
 */
@SuppressWarnings("ALL")
@Component
@ConditionalOnProperty("ajavaer.pay.enable")
@EnableConfigurationProperties({WeixinPayConfig.class})
public class WeixinPay implements InitializingBean {
    private Logger log = LoggerFactory.getLogger(WeixinPay.class);
    @Resource
    private WeixinPayConfig weixinPayConfig;
    @Value("${ajavaer.debug:false}")
    private Boolean isDebug = false;

    private WXPay wxPay;

    public WeixinPay() {
    }

    public WeixinPay(WeixinPayConfig weixinPayConfig) throws Exception {
        this.weixinPayConfig = weixinPayConfig;
        afterPropertiesSet();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        wxPay = new WXPay(weixinPayConfig.toWXPayConfig(), weixinPayConfig.getNotifyUrl(), true, weixinPayConfig.getUseSandbox());
    }

    /**
     * 统一下单
     *
     * @param weixinUnifiedOrderRequest
     * @return
     */
    public Message<WeixinUnifiedOrderResponse> unifiedOrder(WeixinUnifiedOrderRequest weixinUnifiedOrderRequest) {
        try {
            if (isDebug) {
                log.debug("[Pay Weixin] unifiedOrder params:" + JsonTools.beanToJson(weixinUnifiedOrderRequest));
            }
            Map<String, String> resp = wxPay.unifiedOrder(weixinUnifiedOrderRequest.toMap());
            WeixinUnifiedOrderResponse response = new WeixinUnifiedOrderResponse();
            BeanUtils.populate(response, resp);
            return Message.result(response);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 提交付款码支付
     *
     * @see <a href='https://pay.weixin.qq.com/wiki/doc/api/micropay_sl.php?chapter=9_10&index=1'>文档</a>
     */
    @Deprecated
    public <T> Message micropay(WeixinMicropayRequest request, T orderEntity) {
        try {
            if (isDebug) {
                log.debug("[Pay Weixin] micropay params:" + JsonTools.beanToJson(request));
            }
            Map<String, String> resp = wxPay.microPay(request.toMap());
            WeixinOrderQueryResponse weixinOrderQueryResponse = new WeixinOrderQueryResponse();
            BeanUtils.populate(weixinOrderQueryResponse, resp);
            boolean signatureValid = WXPayUtil.isSignatureValid(resp, weixinPayConfig.getSecret(), WXPayConstants.SignType.HMACSHA256);
            if (!signatureValid) {
                log.error("wxPayCallBack sign fail:{}", resp);
                return Message.fail("签名错误");
            }
            return Message.result(weixinOrderQueryResponse);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Message.fail(e.getMessage());
        }
    }

    /**
     * app下单后,使用prepayid生成带签名数据
     *
     * @param prepayId
     * @return
     */
    public WeixinAppOrderResponse appOrderSignResponse(String prepayId) {
        //计算签名
        Map<String, String> doWeixinPay = new HashMap<>();
        doWeixinPay.put("appid", weixinPayConfig.getAppid());
        doWeixinPay.put("partnerid", weixinPayConfig.getMchid());
        doWeixinPay.put("prepayid", prepayId);
        doWeixinPay.put("package", "Sign=WXPay");
        doWeixinPay.put("timestamp", WXPayUtil.getCurrentTimestamp() + "");
        doWeixinPay.put("noncestr", WXPayUtil.generateNonceStr());
        String sign = null;
        try {
            sign = WXPayUtil.generateSignature(doWeixinPay, weixinPayConfig.getSecret(), WXPayConstants.SignType.HMACSHA256);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //构造返回
        WeixinAppOrderResponse response = new WeixinAppOrderResponse();
        response.setAppid(weixinPayConfig.getAppid());
        response.setPartnerid(weixinPayConfig.getMchid());
        response.setPrepayid(prepayId);
        response.setNoncestr(WXPayUtil.generateNonceStr());
        response.setTimestamp(WXPayUtil.getCurrentTimestamp() + "");
        response.setPackageValue("Sign=WXPay");
        response.setSign(sign);
        return response;
    }

    /**
     * 订单查询
     *
     * @param weixinOrderQueryRequest
     * @return
     */
    public Message<WeixinOrderQueryResponse> orderQuery(WeixinOrderQueryRequest weixinOrderQueryRequest) {
        try {
            if (isDebug) {
                log.debug("[Pay Weixin] orderQuery params:" + JsonTools.beanToJson(weixinOrderQueryRequest));
            }
            Map<String, String> resp = wxPay.orderQuery(weixinOrderQueryRequest.toMap());
            WeixinOrderQueryResponse response = new WeixinOrderQueryResponse();
            BeanUtils.populate(response, resp);
            return Message.result(response);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 关闭订单
     * <p>
     * 以下情况需要调用关单接口：商户订单支付失败需要生成新单号重新发起支付，要对原订单号调用关单，避免重复支付；系统下单后，用户支付超时，系统退出不再受理，避免用户继续，请调用关单接口。
     * 注意：订单生成后不能马上调用关单接口，最短调用时间间隔为5分钟。
     * </p>
     *
     * @param weixinCloseOrderRequest
     * @return
     */
    public Message<WeixinCloseOrderResponse> closeOrder(WeixinCloseOrderRequest weixinCloseOrderRequest) {
        try {
            if (isDebug) {
                log.debug("[Pay Weixin] closeOrder params:" + JsonTools.beanToJson(weixinCloseOrderRequest));
            }
            Map<String, String> resp = wxPay.closeOrder(weixinCloseOrderRequest.toMap());
            WeixinCloseOrderResponse response = new WeixinCloseOrderResponse();
            BeanUtils.populate(response, resp);
            return Message.result(response);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 退款
     */
    public Message<WeixinRefundResponse> refund(WeixinRefundRequest weixinRefundRequest) throws Exception {
        if (isDebug) {
            log.debug("[Pay Weixin] refund params:" + JsonTools.beanToJson(weixinRefundRequest));
        }
        WeixinRefundResponse response = new WeixinRefundResponse();
        Map<String, String> refund = wxPay.refund(weixinRefundRequest.toMap());
        BeanUtils.populate(response, refund);
        return Message.result(response);
    }

    /**
     * 退款查询
     *
     * @param request
     * @return
     * @throws Exception
     */
    public Message<WeixinRefundQueryResponse> refundQuery(WeixinRefundQueryRequest request) throws Exception {
        if (isDebug) {
            log.debug("[Pay Weixin] refundQuery params:" + JsonTools.beanToJson(request));
        }
        Map<String, String> refundQuery = wxPay.refundQuery(request.toMap());
        return Message.result(new WeixinRefundQueryResponse(refundQuery));
    }

    /**
     * 获取调用凭证
     * <p>
     * 返回的接口凭证authinfo，可以在expires_in指定的有效期内，同一台机具上重复使用。
     * </p>
     *
     * @param request
     * @return
     * @throws Exception
     * @see <a href='https://pay.weixin.qq.com/wiki/doc/wxfacepay/develop/sdk-android.html#%E8%8E%B7%E5%8F%96%E6%95%B0%E6%8D%AE-getwxpayfacerawdata'>官方文档</a>
     */
    public Message<WeixinFaceAuthInfoResponse> faceAuthInfo(WeixinFaceAuthInfoRequest request) throws Exception {
        if (isDebug) {
            log.debug("[Pay Weixin] refundQuery params:" + JsonTools.beanToJson(request));
        }
        Map<String, String> response = wxPay.faceAuthInfo(request.toMap());
        return Message.result(new WeixinFaceAuthInfoResponse(response));
    }


    /**
     * 退款通知回调
     *
     * @param request
     * @param response
     * @param weixinRefundHandle
     * @param <T>
     */
    public <T> void refundCallBack(HttpServletRequest request, HttpServletResponse response, WeixinRefundHandle<T> weixinRefundHandle) throws Exception {
        String res = ServletTools.getRequestBody(request);
        if (StringTools.isBlank(res)) {
            ServletTools.printString(response, WXPayUtil.mapToXml(WeixinMsgResponse.fail("数据为空").toMap()));
            return;
        }
        Map<String, String> data = WXPayUtil.xmlToMap(res);
        if (isDebug) {
            log.debug("[Pay Weixin] refundCallBack params:" + JsonTools.beanToJson(data));
        }
        weixinRefundHandle.onMessage(data);
        WeixinRefundNoticeResponse weixinRefundNoticeResponse = new WeixinRefundNoticeResponse();
        BeanUtils.populate(weixinRefundNoticeResponse, data);

        String returnCode = weixinRefundNoticeResponse.getReturn_code();
        if (StringTools.isNotBlank(returnCode) && returnCode.equalsIgnoreCase("FAIL")) {
            log.error("refundCallBack returnCode fail msg:" + JsonTools.beanToJson(weixinRefundNoticeResponse));
            return;
        }
        String appid = weixinPayConfig.getAppid();
        if (StringTools.isBlank(appid) || !weixinRefundNoticeResponse.getAppid().equalsIgnoreCase(appid)) {
            log.error("appid or mchId is not match " + JsonTools.beanToJson(weixinRefundNoticeResponse));
            return;
        }
        String mchId = weixinPayConfig.getMchid();
        if (StringTools.isNotBlank(mchId) && !weixinRefundNoticeResponse.getMch_id().equalsIgnoreCase(mchId)) {
            log.error("mchId dose not match " + JsonTools.beanToJson(weixinRefundNoticeResponse));
        }
        WeixinRefundNoticeRecord refundRecord = weixinRefundNoticeResponse.getRefundRecord(weixinPayConfig.getSecret());
        if (refundRecord == null) {
            log.error("refundCallBack decode fail:{}", JsonTools.beanToJson(data));
            return;
        }
        doRefundCallback(weixinRefundHandle, refundRecord);
    }

    /**
     * 退款回调方法
     *
     * @param weixinRefundHandle
     * @param refundRecord
     * @param <T>
     * @throws Exception
     */
    public <T> void doRefundCallback(WeixinRefundHandle<T> weixinRefundHandle, WeixinRefundNoticeRecord refundRecord) throws Exception {
        T refundEntity = weixinRefundHandle.getByOutTradeNo(refundRecord.getOut_trade_no());
        boolean checkFee = weixinRefundHandle.checkOrderByAmount(refundEntity, new BigDecimal(refundRecord.getRefund_fee()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP));
        if (!checkFee) {
            log.error("refundCallBack total_fee fail:{}", refundRecord);
            return;
        }
        weixinRefundHandle.refundBusiness(refundEntity, refundRecord);
    }

    /**
     * 企业付款到零钱
     *
     * @param request
     * @return
     */
    public WeixinCompanyPayResponse companyPayTransfers(WeixinCompanyPayRequest request) {
        WeixinCompanyPayResponse response = null;
        try {
            request.setMch_appid(weixinPayConfig.getAppid());
            Map<String, String> payTransfers = wxPay.companyPayTransfers(request.toMap());
            response = new WeixinCompanyPayResponse(payTransfers);
            if (StringTools.isNotBlank(response.getReturn_code()) && response.getReturn_code().equalsIgnoreCase("FAIL")) {
                log.error("companyPayTransfers fail res:" + JsonTools.beanToJson(response));
                throw new RuntimeException("微信支付系统异常，msg:" + response.getReturn_msg());
            }
        } catch (Exception e) {
            log.error("companyPayTransfers fail msg:" + e.getMessage(), e);
            throw new RuntimeException("companyPayTransfers fail msg:" + e.getMessage());
        }
        return response;
    }
}
